06. Numpy-数组运算

本章概要

  • 学习材料:多资产价格数组与交易日轴。
  • 本章任务:运行 lst-numpy-axis-statisticslst-stock-risk-return-analysis,按正确 axis 计算收益、均值与波动。
  • 完成后你将得到:每资产收益率、均值、标准差和下行风险表。
  • 自我检查:用单列手算均值/标准差并核对 axis 输出长度;价格非正或样本不足时先检查原因。
  • 拓展练习:把风险收益计算拓展应用到另一组三资产。

本章概览:NumPy数组运算

NumPy的核心优势在于其强大的向量化运算能力。

  • 统计运算:最大值、最小值、均值、方差、标准差
  • 数学运算:算术运算、对数、指数、取整
  • 金融指标计算:收益率、波动率、夏普比率
  • 集合运算:交集、并集、差集
  • 比较运算与布尔索引:条件筛选
  • 排序与搜索:排序、极值定位、条件替换

向量化运算为何如此快速?

与Python循环相比,NumPy运算在底层使用优化的C/Fortran代码,可以提供数十倍甚至上百倍的性能提升。

  • 单指令多数据(SIMD):一条CPU指令同时处理多个数值
  • 连续内存布局:提高CPU缓存命中率
  • 底层C实现:避免Python解释器的逐行开销

在金融大数据处理中,向量化运算是必须掌握的核心技能。

运行前预测|平台任务:股票统计量计算

  • 输入预测:运行前先写出 stock_pstock_returnp_maxreturn_avg 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到“中国建筑8月最高股价为{p_max}”相关结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务:股票统计量计算”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务:股票统计量计算

展开完整代码(投影默认折叠)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import numpy as np  # 导入NumPy数值计算库
# 已知数据
stock_p=np.array([4.98,5.02,4.95,4.91,4.98,4.92,4.88,4.92,4.88,4.82,4.85,4.89,4.91,4.86,4.84,4.92,5.01,5.04])
# 创建NumPy数组stock_return
stock_return=np.array([-0.7968,1.4141,0.8147,-1.4056,1.2195,0.8197,-0.813,0.8197,1.2448,-0.6186,-0.818,-0.4073,1.0288,0.4132,-1.626,-1.7964,-0.5952,-1.5625])
# 1. 计算股票的最高价并输出
p_max=stock_p.max()
print(f"中国建筑8月最高股价为{p_max}")  # 输出中国建筑8月最高股价为
# 2. 计算股票的平均涨跌幅,使用round()函数保留三位小数并输出
return_avg=stock_return.mean().round(3)
print(f"平均涨跌幅为{return_avg}")  # 输出平均涨跌幅为
# 3. 计算股票涨跌幅的方差和标准差,使用round()函数并保留3位小数
return_var=stock_return.var().round(3)
return_std=stock_return.std().round(3)  # 计算标准差并四舍五入
print(f"涨跌幅的方差{return_var},标准差{return_std}")  # 输出涨跌幅的方差

任务复盘|平台任务:股票统计量计算

运行后核对:核对 stock_pstock_returnp_maxreturn_avg 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:把输入表替换为本地中国上市公司数据的同结构子集;指出必须保持的字段、数据类型和质量检查。

平台任务代码解析:统计函数

方法 功能 等价写法
.max() 最大值 np.max(arr)
.min() 最小值 np.min(arr)
.mean() 算术平均 np.mean(arr)
.var() 方差 np.var(arr)
.std() 标准差 np.std(arr)
.round(n) 四舍五入到n位 np.round(arr, n)

方差与标准差的金融含义

方差衡量数据偏离均值的程度:

\[ \large{ \text{Var} = \frac{1}{n} \sum_{i=1}^{n}(x_i - \bar{x})^2 } \]

标准差是方差的平方根,与原始数据同单位:

\[ \large{ \text{Std} = \sqrt{\text{Var}} } \]

  • 在金融中,标准差即波动率,是风险的核心度量指标
  • NumPy默认 ddof=0(总体方差),金融分析常用 ddof=1(样本方差)

多维数组统计:axis参数

axis参数决定沿哪个方向聚合计算:

  • axis=0:垂直方向(跨行),对每列计算
  • axis=1:水平方向(跨列),对每行计算
  • axis=None:对所有元素计算(默认)

金融场景对应

  • axis=0:多只股票同一时间的市场平均表现
  • axis=1:单只股票的时间序列特征

多维数组统计:代码演示

Listing 1: 沿指定轴计算统计量
import numpy as np

# 3只股票 × 4个交易日的收益率矩阵
returns = np.array([
    [0.05, 0.03, -0.02, 0.04],  # 股票A
    [0.02, 0.01, 0.03, -0.01],  # 股票B
    [-0.01, 0.02, 0.01, 0.05]   # 股票C
])

# axis=0:跨行计算 → 每日市场平均收益率
daily_avg = returns.mean(axis=0)
print(f'每日平均收益率: {daily_avg}')

# axis=1:跨列计算 → 每只股票平均收益率
stock_avg = returns.mean(axis=1)
print(f'每只股票平均收益率: {stock_avg}')
每日平均收益率: [0.02       0.02       0.00666667 0.02666667]
每只股票平均收益率: [0.025  0.0125 0.0175]

复合累积收益率:cumprod

Listing 2: 使用cumprod计算复合累积收益率
展开实现代码
# 先把简单收益率转为增长因子,再沿axis=1方向连乘并减1
cumulative_returns = (1 + returns).cumprod(axis=1) - 1
print(f'复合累积收益率:\n{cumulative_returns}')
# 例如股票A第3列 = 1.05 × 1.03 × 0.98 - 1 = 0.05987
复合累积收益率:
[[ 0.05        0.0815      0.05987     0.1022648 ]
 [ 0.02        0.0302      0.061106    0.05049494]
 [-0.01        0.0098      0.019898    0.0708929 ]]
  • (1 + r).cumprod(axis=1) - 1:沿行方向复合累乘
  • 口径边界:简单收益率用复利口径;只有对数收益率才可以通过求和跨期聚合
  • 手算核对[+5%, +3%, -2%] 的三期收益为 \(1.05\times1.03\times0.98-1=5.987\%\)

算术运算:元素级运算

NumPy支持元素级的算术运算,这是向量化的核心。

运算符 NumPy函数 描述
+ np.add 加法
- np.subtract 减法
* np.multiply 乘法
/ np.divide 除法
** np.power 幂运算

两个数组形状必须相同,或满足广播规则

算术运算:加法与减法

Listing 3: 数组加法与减法示例
展开实现代码
import numpy as np

# 5个交易日的股价数据
prices = np.array([10, 20, 30, 40, 50])

# 加法:价格调整
adjustment = np.array([1, 2, 3, 4, 5])
new_prices = prices + adjustment
print(f'调整后价格: {new_prices}')

# 减法:相邻日价差
price_diff = prices[1:] - prices[:-1]
print(f'价格变化: {price_diff}')
调整后价格: [11 22 33 44 55]
价格变化: [10 10 10 10]
  • 加法应用:除权除息后的价格调整
  • 减法应用:计算价格动量、技术分析指标

算术运算:乘法与除法

Listing 4: 数组乘法与除法示例
# 乘法:计算持仓市值
shares = np.array([100, 200, 150])
position_value = prices[:3] * shares
print(f'持仓价值: {position_value}')

# 除法:计算简单收益率
# 公式:return = (P_t - P_{t-1}) / P_{t-1}
simple_returns = (prices[1:] - prices[:-1]) / prices[:-1]
print(f'简单收益率: {simple_returns}')
持仓价值: [1000 4000 4500]
简单收益率: [1.         0.5        0.33333333 0.25      ]
  • 乘法应用:持仓价值 = 股价 × 持仓数量
  • 除法应用:计算日收益率

幂运算:复利计算

Listing 5: 幂运算实现复利计算
# 计算1到5年的复利因子(年化收益率5%)
compound = (1 + 0.05) ** np.arange(1, 6)
print(f'5年复利因子: {compound}')
5年复利因子: [1.05       1.1025     1.157625   1.21550625 1.27628156]
  • 第1年末:本金变为 \(1.05\)
  • 第5年末:本金变为 \(1.2763\)
  • 应用:复利计算、未来价值预测、投资回报规划

数学函数:对数收益率

Listing 6: 对数收益率计算
# 5个交易日的股价
prices = np.array([100, 105, 98, 102, 110])

# 对数收益率 = ln(P_t) - ln(P_{t-1})
log_returns = np.diff(np.log(prices))
print(f'对数收益率: {log_returns}')
对数收益率: [ 0.04879016 -0.06899287  0.04000533  0.07550755]

对数收益率公式:

\[ \large{ r_{log} = \ln\left(\frac{P_t}{P_{t-1}}\right) = \ln(P_t) - \ln(P_{t-1}) } \]

对数收益率的三大优势

  1. 时间可加性:多期收益率可直接相加

\[ \large{ r_{log,1 \to 3} = r_{log,1 \to 2} + r_{log,2 \to 3} } \]

  1. 倒数价格因子在对数空间中符号相反:价格因子 0.52 的对数分别为 −0.6931+0.6931;因此跌 50% 需要涨 100% 才回到原价

  2. 建模便利但不保证正态:部分高频小变动样本可能更接近对称,仍须用实际样本检验偏度、厚尾与状态变化

应用场景:连续复利模型、Black-Scholes期权定价

指数函数与波动率计算

Listing 7: 指数函数与波动率计算
# 4只股票的年化收益率
annual_returns = np.array([0.05, 0.06, 0.04, 0.07])

# 指数函数:连续复利计算5年累计收益率
cumulative_5yr = np.exp(annual_returns * 5) - 1
print(f'5年累计收益率: {cumulative_5yr}')

# 波动率计算
volatility = np.std(annual_returns)
variance = volatility ** 2
print(f'波动率: {volatility:.4f}')
print(f'方差: {variance:.4f}')
5年累计收益率: [0.28402542 0.34985881 0.22140276 0.41906755]
波动率: 0.0112
方差: 0.0001

取整函数

Listing 8: 取整函数对比
prices_float = np.array([100.56, 105.23, 98.87])

print(f'向下取整: {np.floor(prices_float)}')
print(f'向上取整: {np.ceil(prices_float)}')
print(f'四舍五入: {np.round(prices_float)}')
向下取整: [100. 105.  98.]
向上取整: [101. 106.  99.]
四舍五入: [101. 105.  99.]
函数 功能 金融应用
np.floor() 向下取整 保守估计所需资金
np.ceil() 向上取整 确保资金充足
np.round() 四舍五入 报价、结果展示

金融案例:股票风险收益分析

Listing 9: 综合运用NumPy进行股票风险收益分析
展开实现代码
import numpy as np

# 中国建筑8月股价数据(18个交易日)
stock_p = np.array([4.98, 5.02, 4.95, 4.91, 4.98, 4.92, 4.88, 4.92,
                    4.88, 4.82, 4.85, 4.89, 4.91, 4.86, 4.84, 4.92,
                    5.01, 5.04])

# 计算日收益率
daily_returns = (stock_p[1:] - stock_p[:-1]) / stock_p[:-1]

# 基本统计量
mean_return = daily_returns.mean()
std_return = daily_returns.std()
# 年化波动率 = 日波动率 × √252
annualized_vol = std_return * np.sqrt(252)

print('=' * 50)
print('中国建筑8月风险收益分析')
print('=' * 50)
print(f'日平均收益率: {mean_return:.4%}')
print(f'日收益率标准差: {std_return:.4%}')
print(f'年化波动率: {annualized_vol:.4%}')
==================================================
中国建筑8月风险收益分析
==================================================
日平均收益率: 0.0761%
日收益率标准差: 1.0575%
年化波动率: 16.7876%

风险收益分析(续):价格与下行风险

Listing 10: 价格统计与下行风险指标
展开实现代码
# 价格统计
max_price = stock_p.max()
min_price = stock_p.min()
price_range = max_price - min_price
print(f'最高价: {max_price:.2f}元')
print(f'最低价: {min_price:.2f}元')
print(f'价格区间: {price_range:.2f}元')

# 下行风险:只考虑下跌日
downside_returns = daily_returns[daily_returns < 0]
downside_risk = downside_returns.std() * np.sqrt(252)
print(f'下行波动率: {downside_risk:.4%}')

# 夏普比率(假设无风险利率3%)
sharpe_ratio = (mean_return * 252 - 0.03) / annualized_vol
print(f'夏普比率: {sharpe_ratio:.2f}')
最高价: 5.04元
最低价: 4.82元
价格区间: 0.22元
下行波动率: 4.6681%
夏普比率: 0.96

夏普比率的含义

夏普比率衡量每承担1单位风险获得的超额收益

\[ \large{ \text{Sharpe} = \frac{R_p - R_f}{\sigma_p} } \]

夏普比率 评价
> 1 优秀
0.5 ~ 1 良好
0 ~ 0.5 一般
< 0 较差(回报低于无风险利率)

集合运算:投资组合分析

Listing 11: NumPy集合运算在股票筛选中的应用
展开实现代码
import numpy as np

# 两个投资组合的持仓
portfolio_a = np.array(['600519.SH', '000858.SZ', '600036.SH', '000002.SZ'])
portfolio_b = np.array(['600036.SH', '601318.SH', '000858.SZ', '600000.SH'])

# 交集:共同持仓
common = np.intersect1d(portfolio_a, portfolio_b)
print(f'共同持仓: {common}')

# 并集:所有股票(去重)
all_stocks = np.union1d(portfolio_a, portfolio_b)
print(f'所有股票: {all_stocks}')

# 差集:独有持仓
unique_a = np.setdiff1d(portfolio_a, portfolio_b)
unique_b = np.setdiff1d(portfolio_b, portfolio_a)
print(f'A组合独有: {unique_a}')
print(f'B组合独有: {unique_b}')
共同持仓: ['000858.SZ' '600036.SH']
所有股票: ['000002.SZ' '000858.SZ' '600000.SH' '600036.SH' '600519.SH' '601318.SH']
A组合独有: ['000002.SZ' '600519.SH']
B组合独有: ['600000.SH' '601318.SH']

集合运算函数总结

函数 功能 金融应用
np.intersect1d 交集 找出共同持仓
np.union1d 并集 构建完整股票池
np.setdiff1d 差集 识别独有持仓
  • 投资组合重叠度分析:避免过度集中风险
  • 策略差异分析:了解不同投资经理的选股偏好

比较运算:生成布尔数组

Listing 12: NumPy比较运算与条件筛选
展开实现代码
import numpy as np

# 5只股票的价格
prices = np.array([10.5, 20.3, 15.8, 25.2, 18.6])

# 比较运算 → 返回布尔数组
above_20 = prices > 20
print(f'价格>20元: {above_20}')

# 布尔索引:提取满足条件的元素
expensive_stocks = prices[prices > 20]
print(f'高价股票: {expensive_stocks}')
价格>20元: [False  True False  True False]
高价股票: [20.3 25.2]
  • 比较运算符(>, <, >=, <=, ==, !=)返回布尔数组
  • 布尔数组可直接作为索引,提取 True 位置的元素

多条件筛选

Listing 13: 多条件组合筛选
# 筛选价格在15-25元之间的股票
filtered = prices[(prices >= 15) & (prices <= 25)]
print(f'价格在15-25元之间: {filtered}')
价格在15-25元之间: [20.3 15.8 18.6]

逻辑运算符

运算符 含义 说明
& 逻辑与(AND) 两个条件都满足
\| 逻辑或(OR) 至少一个条件满足
~ 逻辑非(NOT) 条件取反

注意:条件表达式必须加括号,因为位运算符优先级高于比较运算符。

排序:np.sort 与 argsort

  • np.sort():返回新数组,原数组不变
  • .sort()原地排序,直接修改原数组
  • np.argsort():返回排序后的索引

搜索极值与条件替换

Listing 15: 极值搜索与条件替换
展开实现代码
# 搜索极值位置
max_idx = np.argmax(returns)
min_idx = np.argmin(returns)
print(f'最大值位置: {max_idx}, 值: {returns[max_idx]}')
print(f'最小值位置: {min_idx}, 值: {returns[min_idx]}')

# np.where:条件替换(三目运算)
adjusted = np.where(returns > 0, returns, 0)
print(f'负收益调整为0: {adjusted}')
最大值位置: 3, 值: 0.07
最小值位置: 1, 值: -0.02
负收益调整为0: [0.05 0.   0.03 0.07 0.   0.04]
  • np.argmax() / np.argmin():返回极值的索引位置
  • np.where(条件, True值, False值):条件替换
  • 金融应用:止损策略(限制亏损为0)

性能对比:循环 vs 向量化

Listing 16: NumPy性能优化最佳实践
展开实现代码
import numpy as np
import time

n = 1000000
arr1 = np.random.randn(n)
arr2 = np.random.randn(n)

# Python循环
start = time.time()
result_slow = []
for i in range(n):
    result_slow.append(arr1[i] + arr2[i])
slow_time = time.time() - start

# NumPy向量化
start = time.time()
result_fast = arr1 + arr2
fast_time = time.time() - start

print(f'循环时间: {slow_time:.4f}秒')
print(f'向量化时间: {fast_time:.4f}秒')
print(f'加速比: {slow_time/fast_time:.1f}倍')
循环时间: 0.2271秒
向量化时间: 0.0018秒
加速比: 126.6倍

性能优化三条建议

  1. 使用向量化运算:避免Python for 循环,直接用NumPy运算符

  2. 就地操作节省内存

# 慢(创建新数组)
result = arr * 2
# 快(原地修改)
arr *= 2

性能优化:预分配数组

  1. 预分配数组
# 慢(动态增长列表)
result = []
for i in range(n):
    result.append(i ** 2)
# 快(预分配空间)
result = np.empty(n)
for i in range(n):
    result[i] = i ** 2

本章总结

类别 核心函数 金融应用
统计运算 mean, std, var, max, min 收益率、波动率
数学函数 log, exp, sqrt, abs 对数收益率、复利
集合运算 intersect1d, union1d, setdiff1d 组合分析
比较与索引 >, <, &, \|, 布尔索引 条件筛选
排序与搜索 sort, argsort, argmax, where 排名、止损
性能优化 向量化、就地操作、预分配 大数据处理

随堂练习

  • 问题 1|需要准备哪些数据?:多资产价格数组与交易日轴。
  • 问题 2|需要完成哪些操作?:运行 lst-numpy-axis-statisticslst-stock-risk-return-analysis,按正确 axis 计算收益、均值与波动。
  • 问题 3|应得到哪些结果?:每资产收益率、均值、标准差和下行风险表。
  • 问题 4|怎样确认结果可靠?:用单列手算均值/标准差并核对 axis 输出长度;价格非正或样本不足时先检查原因。
  • 问题 5|换一个情境,怎样继续应用?:把风险收益计算拓展应用到另一组三资产。
  • 作答提示:请依次写清所用数据、分析过程、所得结果、核对方法和拓展思考。课程所需数据见前言中的下载入口;教学平台固定题按页面说明完成。

教师参考解答|答案与说明 1

  • 补充练习|按资产轴计算收益与风险

教师参考解答|代码 1

展开代码(代码区可独立滚动)
import numpy as np  # 导入数组计算库
prices=np.array([[100.0,102.0,101.0,104.0],[50.0,49.0,51.0,50.0],[80.0,84.0,82.0,86.0]])  # 定义三资产价格并让时间沿列
assert prices.ndim==2 and prices.shape[1]>=3 and np.all(prices>0)  # 执行维度、样本与正价格输入要求
returns=prices[:,1:]/prices[:,:-1]-1  # 沿时间轴计算每资产简单收益
means=returns.mean(axis=1)  # 沿交易期轴计算每资产均值
volatility=returns.std(axis=1,ddof=1)  # 沿交易期轴计算每资产样本标准差
downside=np.sqrt(np.mean(np.minimum(returns,0.0)**2,axis=1))  # 沿交易期轴计算每资产下行风险
manual=prices[0,1:]/prices[0,:-1]-1  # 对首资产独立手算收益
assert np.allclose(returns[0],manual) and len(means)==prices.shape[0]  # 核对轴方向与输出长度
assert np.isclose(means[0],manual.mean()) and np.isclose(volatility[0],manual.std(ddof=1))  # 核对首资产统计量
print({'均值':means.round(6),'标准差':volatility.round(6),'下行风险':downside.round(6)})  # 输出风险收益结果

教师参考解答|答案与说明 2

  • 解释与拓展应用答案:价格矩阵行是资产、列是交易日,因此收益沿列相邻相除,统计量也沿 axis=1 聚合;拓展应用时保持同一轴要求并重复首列手算核对。

  • 所用数据与字段:简单收益率矩阵,时间沿列

  • 参考代码或推导(平台保护块之外):

教师参考解答|代码 2

展开代码(代码区可独立滚动)
import numpy as np  # 导入数组库
new_prices=np.array([[20.0,21.0,20.5,22.0],[30.0,29.0,30.0,31.0],[60.0,61.0,63.0,62.0]])  # 定义另一组三资产价格
assert np.all(new_prices>0) and new_prices.shape==(3,4)  # 执行新案例数据输入要求
new_returns=new_prices[:,1:]/new_prices[:,:-1]-1  # 沿同一时间轴计算新案例收益
new_summary=np.column_stack([new_returns.mean(1),new_returns.std(1,ddof=1),np.sqrt(np.mean(np.minimum(new_returns,0.0)**2,axis=1))])  # 生成均值、标准差与下行风险表
assert new_summary.shape==(3,3) and np.isfinite(new_summary).all()  # 检查三资产三指标且有限
assert np.allclose(new_returns[0],new_prices[0,1:]/new_prices[0,:-1]-1)  # 另行核对新案例首资产
print(new_summary.round(6))  # 输出新案例风险收益表

教师参考解答|答案与说明 3

  • 参考结果:每资产一行的均值、样本标准差和下行风险表
  • 边界 / 局限:价格非正或少于三个交易日时应先检查原因,不输出风险表
  • 常见错误:资产轴与时间轴写反;标准差自由度不一致;把负收益均值当下行风险